#version 300 es
precision highp float;
precision mediump sampler2DShadow;
precision mediump sampler2D;
//#pragma optionNV(unroll all)

#define N_LIGHTS 3

in vec2 frag_uv;
in vec3 frag_world_position;
in vec3 frag_view;
in vec3 frag_normal;
in vec3 frag_tangent;

layout(location = 0) out vec4 out_color;
layout(location = 1) out float out_depth;
//layout(location = 2) out vec2 out_velocity;
layout(location = 2) out vec4 out_specular_color;
//layout(location = 4) out vec4 out_shadow;

struct Light {
    vec3 position;
    vec3 direction;
    float falloffStart;
    float falloffWidth;
    vec3 color;
    float attenuation;
    float farPlane;
    float bias;
    mat4 viewProjection;
};

uniform sampler2D diffuse_tex;
uniform sampler2D specularAO_tex;
uniform sampler2D normal_map_tex;
uniform sampler2D beckmann_tex;
uniform samplerCube irradiance_tex;
//uniform sampler2DShadow shadow_maps[N_LIGHTS];
uniform sampler2DShadow shadow_maps_0;
uniform sampler2DShadow shadow_maps_1;
uniform sampler2DShadow shadow_maps_2;
//uniform sampler2D depth_textures[N_LIGHTS]; // the same textures with shadow_maps
uniform sampler2D depth_textures_0;
uniform sampler2D depth_textures_1;
uniform sampler2D depth_textures_2;

uniform float bumpiness;	// for normal map
uniform float specularIntensity;
uniform float specularRoughness;
uniform float specularFresnel;
uniform float translucency;
uniform float sssWidth;
uniform float ambient;

uniform bool sssEnabled;
uniform bool sssTranslucencyEnabled;
uniform bool separate_speculars;

uniform Light lights[N_LIGHTS];

#define SSSSSaturate(a) clamp(a, 0.0, 1.0)

// get original z [(mvp * v).z] from z in shadowMap
float to_frag_z(float z)
{
    float far = 10.0f;
    float near = 0.1f;
    float frag_z = far*near / (far-z*(far-near));
    return (frag_z-near)*far / (far-near);
}

//-----------------------------------------------------------------------------
// Separable SSS Transmittance Function

vec3 SSSSTransmittance(float translucency, float sssWidth, vec3 worldPosition, vec3 worldNormal, vec3 light, sampler2D shadowMap, mat4 lightViewProjection, float lightFarPlane) {
    /**
     * Calculate the scale of the effect.
     */
    float scale = 8.25 * (1.0 - translucency) / sssWidth;
    
    /**
     * First we shrink the position inwards the surface to avoid artifacts:
     * (Note that this can be done once for all the lights)
     */
    vec4 shrinkedPos = vec4(worldPosition - 0.005 * worldNormal, 1.0);
    
    /**
     * Now we calculate the thickness from the light point of view:
     */
    vec4 shadowPosition = lightViewProjection * shrinkedPos;
    
    
    
    shadowPosition.xy /= shadowPosition.w;
    float d1 = texture(shadowMap, shadowPosition.xy).r;
    d1 = to_frag_z(d1);
    float d2 = shadowPosition.z;
    float d = scale * abs(d1 - d2);
    
    /**
     * Armed with the thickness, we can now calculate the color by means of the
     * precalculated transmittance profile.
     * (It can be precomputed into a texture, for maximum performance):
     */
    float dd = -d * d;
    //vec3 profile = texture(shadowMap, frag_uv).rgb;
    vec3 profile = vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
    vec3(0.1,   0.336, 0.344) * exp(dd / 0.0484) +
    vec3(0.118, 0.198, 0.0)   * exp(dd / 0.187)  +
    vec3(0.113, 0.007, 0.007) * exp(dd / 0.567)  +
    vec3(0.358, 0.004, 0.0)   * exp(dd / 1.99)   +
    vec3(0.078, 0.0,   0.0)   * exp(dd / 7.41);
    //vec3 profile = vec3(0);
    
    /**
     * Using the profile, we finally approximate the transmitted lighting from
     * the back of the object:
     */
    return profile * SSSSSaturate(0.3 + dot(light, -worldNormal));
    //return vec3(1);
    //return vec3(textureProj(shadowMap, shadowPosition));
}


vec3 BumpMap(sampler2D normal_tex, vec2 uv)
{
    vec3 bump;
    bump.xy = -1.0 + 2.0 * texture(normal_tex, uv).gr;
    bump.z = sqrt(1.0 - bump.x * bump.x - bump.y * bump.y);
    //bump = -1.0 + 2.0 * texture(normal_tex, uv).rgb;
    return normalize(bump);
}

// H: half
float Fresnel(vec3 H, vec3 View, float f0)
{
    float base = 1.0 - dot(View, H);
    //float exponential = pow(base, 5.0);
    float exponential = base*base;
    exponential = exponential * exponential * base;
    return exponential + f0 * (1.0 - exponential);
}

float SpecularKSK(sampler2D beckmann_tex, vec3 normal, vec3 light, vec3 view, float roughness)
{
    vec3 H = view + light;
    vec3 HN = normalize(H);
    
    float NdotL = max(dot(normal, light), 0.0);
    float NdotH = max(dot(normal, HN), 0.0);
    
    float ph = pow(2.0 * texture(beckmann_tex, vec2(NdotH, roughness)).r, 10.0f);
    float f = mix(0.25, Fresnel(HN, view, 0.028), specularFresnel);
    float ksk = max(ph * f / dot(H, H), 0.0);
    
    return NdotL * ksk;
}


#define EPSILON -0.0000001

// TODO
float ShadowPCF(vec3 world_pos, sampler2DShadow shadow_map, int i, int samples, float width)
{
    return textureProj(shadow_map, lights[i].viewProjection * vec4(world_pos, 1));
}

void process(int i, sampler2D depth_texture, vec4 albedo, float shadow, vec3 normal, vec3 view, float intensity, float roughness)
{
    vec3 L = lights[i].position - frag_world_position.xyz;
    float dist = length(L);
    L /= dist;
    
    float spot = dot(lights[i].direction, -L);
    if (spot > lights[i].falloffStart)
    {
        float curve = min(pow(dist / lights[i].farPlane, 6.0), 1.0);
        float attenuation = mix(1.0 / (1.0 + lights[i].attenuation * dist * dist), 0.0, curve);
        
        spot = clamp((spot - lights[i].falloffStart) / lights[i].falloffWidth , 0.0, 1.0);
        
        vec3 f1 = lights[i].color * attenuation * spot;
        vec3 f2 = albedo.rgb * f1;
        
        vec3 diffuse = vec3(clamp(dot(L, normal), 0.0, 1.0));
        float specular = intensity * SpecularKSK(beckmann_tex, normal, L, view, roughness);
        
        out_specular_color.rgb += shadow * f1 * specular;
        out_color.rgb += shadow * f2 * diffuse;
        if (!separate_speculars)
        {
            out_color.rgb += shadow * f1 * specular;
        }
        
        if (sssEnabled)
        {
            out_color.rgb += f2 * SSSSTransmittance(translucency, sssWidth, frag_world_position.xyz, normalize(frag_normal), L, depth_texture, lights[i].viewProjection, lights[i].farPlane);

        }
    }
}

void main()
{
    vec3 in_normal = normalize(frag_normal);
    vec3 tangent = normalize(frag_tangent);
    vec3 bitangent = normalize(cross(tangent, in_normal));
    mat3 tbn = mat3(tangent, bitangent, in_normal);
    
    vec2 uv_for_dds = vec2(frag_uv.x, 1.0 - frag_uv.y);
    vec3 bump_normal = BumpMap(normal_map_tex, uv_for_dds);
    vec3 tangent_normal = mix(vec3(0, 0, 1), bump_normal, bumpiness);
    vec3 normal = tbn * tangent_normal;
    //vec3 normal = in_normal;
    vec3 view = normalize(frag_view);
    
    vec4 albedo = texture( diffuse_tex, frag_uv );
    vec3 specularAO = texture( specularAO_tex, uv_for_dds).bgr;
    
    float occlusion = specularAO.b;
    float intensity = specularAO.r * specularIntensity;
    float roughness = (specularAO.g / 0.3) * specularRoughness;
    
    out_color = vec4(0, 0, 0, 0);
    out_specular_color = vec4(0, 0, 0, 0);
    
    float shadow[N_LIGHTS];
    shadow[0] = ShadowPCF( frag_world_position, shadow_maps_0, 0, 3, 1.0);
    shadow[1] = ShadowPCF( frag_world_position, shadow_maps_1, 1, 3, 1.0);
    shadow[2] = ShadowPCF( frag_world_position, shadow_maps_2, 2, 3, 1.0);
    vec3 ssss_transmittance;
    
    process(0, depth_textures_0, albedo, shadow[0], normal, view, intensity, roughness);
    process(1, depth_textures_1, albedo, shadow[1], normal, view, intensity, roughness);
    process(2, depth_textures_2, albedo, shadow[2], normal, view, intensity, roughness);
    
    // Add the ambient component:
    vec3 env_irradiance = texture( irradiance_tex, normal ).rgb;
    out_color.rgb += occlusion * ambient * albedo.rgb * env_irradiance;
    
    // Store the SSS strength:
    out_specular_color.a = albedo.a;
    //out_color.a = albedo.a;
    
    // Store the depth value:
    //out_depth = 1.0 / gl_FragCoord.w;	//!
    out_depth = gl_FragCoord.w;
    
    // Compress the velocity for storing it in a 8-bit render target:
    //out_color.a = sqrt(5.0 * length(out_velocity));
    out_color.a = 1.0;
}
